/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.data.redis;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.StringUtils;
import org.tianyun.cloud.config.refresh.smart.SmartBeanDefinition;
import org.tianyun.cloud.data.redis.factory.ObjectRedisTemplate;
import org.tianyun.cloud.data.redis.factory.SpringReactiveRedisConnectionFactory;

import java.util.Map;

/**
 * RabbitMQ装载配置器
 *
 * @auther ebert_chan
 */
public class RedisConnectionFactoryConfigurer {

    private static final String CONNECTION_FACTORY_NAME_SUFFIX = ".redisConnectionFactory";

    private RedisContainerBeanWrapper redisContainerBeanWrapper;

    private ResourceLoader resourceLoader;

    public RedisConnectionFactoryConfigurer(String propertyBeanName, Map<String, Object> attributes, ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
        redisContainerBeanWrapper = new RedisContainerBeanWrapper();
        redisContainerBeanWrapper.setConnectionFactoryBeanName((String) attributes.get("connectionFactory"));
        redisContainerBeanWrapper.setPropertyBeanName(propertyBeanName);
        redisContainerBeanWrapper.setRedisTemplateBeanName((String) attributes.get("redisTemplate"));
        redisContainerBeanWrapper.setStringRedisTemplateBeanName((String) attributes.get("stringRedisTemplate"));
        redisContainerBeanWrapper.setObjectRedisTemplateBeanName((String) attributes.get("objectRedisTemplate"));
        redisContainerBeanWrapper.setReactiveRedisTemplateBeanName((String) attributes.get("reactiveRedisTemplate"));
        redisContainerBeanWrapper.setReactiveStringRedisTemplateBeanName((String) attributes.get("reactiveStringRedisTemplate"));
        redisContainerBeanWrapper.setRefreshKey((String) attributes.get("prefix"));
        redisContainerBeanWrapper.setPrimary((Boolean) attributes.get("primary"));
        if (!StringUtils.hasText(redisContainerBeanWrapper.getConnectionFactoryBeanName())) {
            // 自动定义一个beanName
            redisContainerBeanWrapper.setConnectionFactoryBeanName(propertyBeanName + CONNECTION_FACTORY_NAME_SUFFIX);
        }
    }

    public RedisContainerBeanWrapper getRedisContainerBeanWrapper() {
        return redisContainerBeanWrapper;
    }

    public void registryBean(BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(redisContainerBeanWrapper.getPropertyBeanName())) {
            return;
        }

        BeanDefinition redisConnectionFactory = this.connectionFactoryBeanDefinition(redisContainerBeanWrapper.getPropertyBeanName());
        this.setPrimary(redisConnectionFactory, redisContainerBeanWrapper.getPrimary());
        // 使用SmartBeanDefinition，根据refreshKey动态刷新
        SmartBeanDefinition proxyCachingConnectionFactory = new SmartBeanDefinition(redisContainerBeanWrapper.getRefreshKey(), redisConnectionFactory);
        proxyCachingConnectionFactory.registerBeanDefinition(registry, redisContainerBeanWrapper.getConnectionFactoryBeanName());

        // rabbitTemplate
        if (StringUtils.hasText(redisContainerBeanWrapper.getRedisTemplateBeanName())) {
            BeanDefinition rabbitTemplate = this.redisTemplateBeanDefinition(redisContainerBeanWrapper.getConnectionFactoryBeanName());
            this.setPrimary(rabbitTemplate, redisContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(redisContainerBeanWrapper.getRedisTemplateBeanName(), rabbitTemplate);
        }

        // stringRedisTemplateBeanDefinition
        if (StringUtils.hasText(redisContainerBeanWrapper.getStringRedisTemplateBeanName())) {
            BeanDefinition stringRedisTemplateBeanDefinition = this.stringRedisTemplateBeanDefinition(redisContainerBeanWrapper.getConnectionFactoryBeanName());
            this.setPrimary(stringRedisTemplateBeanDefinition, redisContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(redisContainerBeanWrapper.getStringRedisTemplateBeanName(), stringRedisTemplateBeanDefinition);
        }

        // objectRedisTemplateBeanDefinition
        if (StringUtils.hasText(redisContainerBeanWrapper.getObjectRedisTemplateBeanName())) {
            BeanDefinition objectRedisTemplateBeanDefinition = this.objectRedisTemplateBeanDefinition(redisContainerBeanWrapper.getConnectionFactoryBeanName());
            this.setPrimary(objectRedisTemplateBeanDefinition, redisContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(redisContainerBeanWrapper.getObjectRedisTemplateBeanName(), objectRedisTemplateBeanDefinition);
        }

        // redisTemplateBeanDefinition
        if (StringUtils.hasText(redisContainerBeanWrapper.getReactiveRedisTemplateBeanName())) {
            BeanDefinition redisTemplateBeanDefinition = this.reactiveRedisTemplateBeanDefinition(redisContainerBeanWrapper.getConnectionFactoryBeanName());
            this.setPrimary(redisTemplateBeanDefinition, redisContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(redisContainerBeanWrapper.getReactiveRedisTemplateBeanName(), redisTemplateBeanDefinition);
        }

        // reactiveStringRedisTemplateBeanDefinition
        if (StringUtils.hasText(redisContainerBeanWrapper.getReactiveStringRedisTemplateBeanName())) {
            BeanDefinition reactiveStringRedisTemplateBeanDefinition = this.reactiveStringRedisTemplateBeanDefinition(redisContainerBeanWrapper.getConnectionFactoryBeanName());
            this.setPrimary(reactiveStringRedisTemplateBeanDefinition, redisContainerBeanWrapper.getPrimary());
            registry.registerBeanDefinition(redisContainerBeanWrapper.getReactiveStringRedisTemplateBeanName(), reactiveStringRedisTemplateBeanDefinition);
        }

    }

    private void setPrimary(BeanDefinition beanDefinition, Boolean primary) {
        if (primary != null && primary) {
            beanDefinition.setAutowireCandidate(true);
            beanDefinition.setPrimary(true);
        }
    }

    private BeanDefinition connectionFactoryBeanDefinition(String propertyBeanName) {
        // @formatter:off
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpringReactiveRedisConnectionFactory.class)
                .addConstructorArgReference(propertyBeanName);
		// @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition redisTemplateBeanDefinition(String connectionFactoryBeanName) {
        // @formatter:off
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisTemplate.class)
                .addPropertyReference("connectionFactory", connectionFactoryBeanName)
                .addDependsOn(connectionFactoryBeanName);
        // @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition stringRedisTemplateBeanDefinition(String connectionFactoryBeanName) {
        // @formatter:off
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StringRedisTemplate.class)
                .addPropertyReference("connectionFactory", connectionFactoryBeanName)
                .addDependsOn(connectionFactoryBeanName);
        // @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition objectRedisTemplateBeanDefinition(String connectionFactoryBeanName) {
        // @formatter:off
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ObjectRedisTemplate.class)
                .addPropertyReference("connectionFactory", connectionFactoryBeanName)
                .addDependsOn(connectionFactoryBeanName);
        // @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition reactiveRedisTemplateBeanDefinition(String connectionFactoryBeanName) {
        JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(resourceLoader.getClassLoader());
        RedisSerializationContext<Object, Object> serializationContext = RedisSerializationContext
                .newSerializationContext()
                .key(jdkSerializer).value(jdkSerializer).hashKey(jdkSerializer).hashValue(jdkSerializer)
                .build();
        // @formatter:off
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ReactiveRedisTemplate.class)
                .addConstructorArgReference(connectionFactoryBeanName)
                .addConstructorArgValue(serializationContext)
                .addDependsOn(connectionFactoryBeanName);
        // @formatter:on
        return builder.getBeanDefinition();
    }

    private BeanDefinition reactiveStringRedisTemplateBeanDefinition(String connectionFactoryBeanName) {
        // @formatter:off
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ReactiveStringRedisTemplate.class)
                .addConstructorArgReference(connectionFactoryBeanName)
                .addDependsOn(connectionFactoryBeanName);
        // @formatter:on
        return builder.getBeanDefinition();
    }

}
